<html>
<head>
  <script type="text/javascript" src="webrtc_test_utilities.js"></script>
  <script type="text/javascript" src="webrtc_test_common.js"></script>
  <script type="text/javascript">
  $ = function(id) {
    return document.getElementById(id);
  };

  var gPeerConnection = null;
  var gCertificate = null;

  // This test creates and sets three offers, calling setConfiguration in
  // between each offer, expecting an ICE restart to be triggered by the next
  // offer.
  async function testSetConfiguration() {
    gPeerConnection = new RTCPeerConnection(
        {iceServers:[], iceTransportPolicy:'all', bundlePolicy:'balanced',
         rtcpMuxPolicy:'require', certificates:[], iceCandidatePoolSize:0});
    // Change ICE candidate pool size, which will succeed before
    // setLocalDescription is called.
    gPeerConnection.setConfiguration(
        {iceServers:[], iceTransportPolicy:'all', bundlePolicy:'balanced',
         rtcpMuxPolicy:'require', certificates:[], iceCandidatePoolSize:1});
    // Now test successful cases of setConfiguration. Changes should trigger an
    // ICE restart in the next offer. To do this, first we need to trigger an
    // initial ICE gathering phase and wait until it completes.

    function waitForState(state) {
      return new Promise((resolve) => {
        gPeerConnection.onicegatheringstatechange = (candidate) => {
          if (gPeerConnection.iceGatheringState === state) {
            resolve();
          }
        };
      });
    }

    let onCompletePromise = waitForState('complete');
    await createOfferAndSetLocalDescription();
    await onCompletePromise;
    onCompletePromise = waitForState('complete');
    // Policy changed.
    gPeerConnection.setConfiguration(
        {iceServers:[], iceTransportPolicy:'relay', bundlePolicy:'balanced',
          rtcpMuxPolicy:'require', certificates:[], iceCandidatePoolSize:1});
    await createOfferAndSetLocalDescription();
    await onCompletePromise;
    // Only wait for 'gathering', since it will take a while for the requests to
    // 'foo.invalid' to time out.
    const onGatheringPromise = waitForState('gathering');
    // Servers changed.
    gPeerConnection.setConfiguration(
        {iceServers:[{urls:'stun:foo.invalid'}], iceTransportPolicy:'all',
          bundlePolicy:'balanced', rtcpMuxPolicy:'require', certificates:[],
          iceCandidatePoolSize:1});
    await createOfferAndSetLocalDescription();
    await onGatheringPromise;
    return logSuccess();
  }

  async function testSetConfigurationErrors() {
    // Generate certificate so we can test the InvalidModificationError from
    // attempting to change certificates.
    let certificate;
    try {
      certificate = await RTCPeerConnection.generateCertificate(
        { name:'ECDSA', namedCurve:'P-256' });
    } catch {
      throw new Error('Failed to generate certificate.');
    }
    gCertificate = certificate;
    return continueTestSetConfigurationErrors();
  }

  // Continued after certificate generated.
  async function continueTestSetConfigurationErrors() {
    gPeerConnection = new RTCPeerConnection(
        {iceServers:[], iceTransportPolicy:'all', bundlePolicy:'balanced',
         rtcpMuxPolicy:'require', certificates:[], iceCandidatePoolSize:1});
    // If bundlePolicy, rtcpMuxPolicy or certificates are changed, an
    // InvalidModificationError should be thrown.
    assertThrows(gPeerConnection.setConfiguration,
                 {iceServers:[], iceTransportPolicy:'all',
                  bundlePolicy:'max-bundle', rtcpMuxPolicy:'require',
                  certificates:[], iceCandidatePoolSize:1});
    assertThrows(gPeerConnection.setConfiguration,
                 {iceServers:[], iceTransportPolicy:'all',
                  bundlePolicy:'balanced', rtcpMuxPolicy:'negotiate',
                  certificates:[]});
    assertThrows(gPeerConnection.setConfiguration,
                 {iceServers:[], iceTransportPolicy:'all',
                  bundlePolicy:'balanced', rtcpMuxPolicy:'require',
                  certificates:[gCertificate], iceCandidatePoolSize:1});
    // Failure to parse URL should result in SyntaxError.
    assertThrows(gPeerConnection.setConfiguration,
                 {iceServers:[{url:'stunnnn:foo.invalid'}],
                  iceTransportPolicy:'all', bundlePolicy:'max-bundle',
                  rtcpMuxPolicy:'require', certificates:[],
                  iceCandidatePoolSize:1});
    // TURN server with missing username should result in InvalidAccessError.
    assertThrows(gPeerConnection.setConfiguration,
                 {iceServers:[{url:'turn:foo.invalid'}],
                  iceTransportPolicy:'all', bundlePolicy:'max-bundle',
                  rtcpMuxPolicy:'require', certificates:[],
                  iceCandidatePoolSize:1});
    // Sanity check that a configuration can be successfully set, and thus
    // there's not something unexpected causing the above exceptions.
    gPeerConnection.setConfiguration(
        {iceServers:[], iceTransportPolicy:'all', bundlePolicy:'balanced',
         rtcpMuxPolicy:'require', certificates:[], iceCandidatePoolSize:1});
    // Lastly: only after applying a local description, changing the candidate
    // pool size is not allowed.
    let offer;
    try {
      offer = await gPeerConnection.createOffer({offerToReceiveAudio:1});
    } catch {
      throw new Error('Failed to generate offer.');
    }

    console.log("Setting offer:\n" + offer.sdp);
    try {
      await gPeerConnection.setLocalDescription(offer);
    } catch {
      throw new Error('Failed to set local description.');
    }

    // Pool size absent, which means it should default to 0, which is
    // different than its current value of 1.
    assertThrows(gPeerConnection.setConfiguration,
                  {iceServers:[], iceTransportPolicy:'all',
                  bundlePolicy:'balanced', rtcpMuxPolicy:'require',
                  certificates:[]});
    return logSuccess();
  }

  function assertThrows(func) {
    try {
      func.apply(arguments.slice(start=1));
    } catch (e) {
      return;
    }
    throw new Error('Expected exception to be thrown by: ' + func);
  }

  // Helper function to create and apply offer.
  async function createOfferAndSetLocalDescription() {
    let offer;
    try {
    offer = await gPeerConnection.createOffer({offerToReceiveAudio:1});
    } catch {
      throw new Error('Failed to generate offer.');
    }

    console.log("Setting offer:\n" + offer.sdp);

    try {
      return gPeerConnection.setLocalDescription(offer);
    } catch {
      throw new Error('Failed to set local description.');
    }
  }

  </script>
</head>
<body>
</body>
</html>
